using System.Drawing;
using System.Xml;
using Wilco.SyntaxHighlighting.Engine.Node;
using Wilco.SyntaxHighlighting.Engine.Node.Implementation.String;
using Wilco.SyntaxHighlighting.Engine.Node.Occurrence;
using Wilco.SyntaxHighlighting.Engine.Scanner;
using Wilco.SyntaxHighlighting.Engine.Tokenizer;

namespace Wilco.SyntaxHighlighting.Engine.Scanner.Implementation
{
    /// <summary>
    /// Represents a string block scanner.
    /// </summary>
    public class StringBlockScanner : ScannerBase
    {
        private StringNode stringNode;
        private string lastToken;

        /// <summary>
        /// Gets or sets the string node.
        /// </summary>
        public StringNode StringNode
        {
            get
            {
                return stringNode;
            }
            set
            {
                if (value != stringNode)
                {
                    stringNode = value;
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="StringBlockScanner"/> class.
        /// </summary>
        public StringBlockScanner() : this(null, null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="StringBlockScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        public StringBlockScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult) : this(tokenizer, scannerResult, new StringNode())
        {
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="StringBlockScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <param name="stringNode">A <see cref="Node.Implementation.String.StringNode"/> object.</param>
        public StringBlockScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult, StringNode stringNode) : base(tokenizer, scannerResult)
        {
            this.stringNode = stringNode;
            lastToken = null;
            SetID("StringBlockScanner");
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new StringBlockScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XmlElement element = (XmlElement)state;
			
            // Load settings.
            FontConverter converter = new FontConverter();
            stringNode.BackColor = ColorTranslator.FromHtml(element.SelectSingleNode("settings/setting[@name='BackColor']").InnerText);
            stringNode.ForeColor = ColorTranslator.FromHtml(element.SelectSingleNode("settings/setting[@name='ForeColor']").InnerText);
            stringNode.Font = (Font)converter.ConvertFromString(element.SelectSingleNode("settings/setting[@name='Font']").InnerText);
            stringNode.NavigateUrl = element.SelectSingleNode("settings/setting[@name='NavigateUrl']").InnerText;

            // Load entities.
            foreach (XmlElement entityElement in element["entities"].ChildNodes)
            {
                stringNode.Entities.Add(new StringEntity(entityElement.Attributes["start"].Value, entityElement.Attributes["end"].Value, entityElement.Attributes["escape"].Value));
            }
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            XmlDocument document = (XmlDocument)container;
            XmlElement element = (XmlElement)base.SaveState(container);
			
            // Save settings.
            XmlElement settingRootElement = document.CreateElement("settings");
            element.AppendChild(settingRootElement);

            FontConverter converter = new FontConverter();
            settingRootElement.AppendChild(CreateSetting(document, "BackColor", ColorTranslator.ToHtml(stringNode.BackColor)));
            settingRootElement.AppendChild(CreateSetting(document, "ForeColor", ColorTranslator.ToHtml(stringNode.ForeColor)));
            settingRootElement.AppendChild(CreateSetting(document, "Font", converter.ConvertToString(stringNode.Font)));
            settingRootElement.AppendChild(CreateSetting(document, "NavigateUrl", stringNode.NavigateUrl));

            // Save entities.
            XmlElement entityRootElement = document.CreateElement("entities");
            element.AppendChild(entityRootElement);
            XmlElement entityElement;
            foreach (StringEntity entity in stringNode.Entities)
            {
                entityElement = document.CreateElement("entity");
                entityElement.SetAttribute("start", entity.Start);
                entityElement.SetAttribute("end", entity.End);
                entityElement.SetAttribute("escape", entity.Escape);
                entityRootElement.AppendChild(entityElement);
            }

            return element;
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="document">The document which will contain the setting.</param>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private static XmlElement CreateSetting(XmlDocument document, string name, string value)
        {
            XmlElement element = document.CreateElement("setting");
            element.SetAttribute("name", name);
            element.InnerText = value;
            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="IScanner"/> implementation will generally have a reference to a 
        /// <see cref="NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!Enabled)
            {
                if (Child != null)
                {
                    Child.Scan(token);
                }
            }
            else
            {
                bool isMatch = false;

                if (lastToken != null && lastToken == "@")
                {
                    for (int i = 0; i < stringNode.Entities.Count; i++)
                    {
                        if ((Tokenizer.Position + stringNode.Entities[i].Start.Length) <= Tokenizer.Source.Length)
                        {
                            if (Tokenizer.GetNextTokens(stringNode.Entities[i].Start.Length) == stringNode.Entities[i].Start)
                            {
                                Tokenizer.MoveTo(Tokenizer.Position + StringNode.Entities[i].Start.Length);
                                int startIndex = Tokenizer.Position;

                                int endOfLineIndex = Tokenizer.Source.Length;

                                string escape = ((StringEntity)StringNode.Entities[i]).Escape;

                                while (Tokenizer.Position <= endOfLineIndex)
                                {
                                    int index = Tokenizer.Source.IndexOf(stringNode.Entities[i].End, Tokenizer.Position, endOfLineIndex - Tokenizer.Position);
                                    if (index > -1)
                                    {
                                        if (stringNode.Entities[i].End == escape && index + escape.Length < Tokenizer.Source.Length && Tokenizer.Source.Substring(index + escape.Length, escape.Length) == escape)
                                        {
                                            Tokenizer.MoveTo(index + escape.Length * 2);

                                            // Error.
                                            if (Tokenizer.Position == Tokenizer.Source.Length)
                                            {
                                                ScannerResult.Add(new Occurrence(startIndex, endOfLineIndex - startIndex, stringNode));
                                            }
                                        }
                                        else if (stringNode.Entities[i].End != escape && Tokenizer.Source.Substring(index - escape.Length, escape.Length) == escape)
                                        {
                                            Tokenizer.MoveTo(index + stringNode.Entities[i].End.Length);

                                            // Error.
                                            if (Tokenizer.Position == Tokenizer.Source.Length)
                                            {
                                                ScannerResult.Add(new Occurrence(startIndex, endOfLineIndex - startIndex, stringNode));
                                            }
                                        }
                                        else
                                        {
                                            // Success.
                                            ScannerResult.Add(new Occurrence(startIndex, index - startIndex, stringNode));
                                            Tokenizer.MoveTo(index + stringNode.Entities[i].End.Length);
                                            break;
                                        }
                                    }
                                    else
                                    {
                                        // Error.
                                        ScannerResult.Add(new Occurrence(startIndex, endOfLineIndex - startIndex, stringNode));
                                        Tokenizer.MoveTo(endOfLineIndex - 1);
                                        break;
                                    }
                                }

                                isMatch = true;
                                break;
                            }
                        }
                    }
                }

                lastToken = token;

                if (!isMatch)
                {
                    if (Child != null)
                    {
                        Child.Scan(token);
                    }
                }
            }
        }

        /// <summary>
        /// Resets the scanner.
        /// </summary>
        public override void Reset()
        {
            lastToken = null;
        }
    }
}